package edu.pitt.myptm.datamanager;

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Hashtable;
import java.util.logging.FileHandler;
import java.util.logging.Logger;
import java.util.logging.SimpleFormatter;

import edu.pitt.myptm.BeginCommand;
import edu.pitt.myptm.CommandFactory;
import edu.pitt.myptm.DataItem;
import edu.pitt.myptm.MyCommand;
import edu.pitt.myptm.utils.DBUtils;

public class DataManager {

	private int pageLimit;

	private BufferManager buffer;

	public DataManager() {
		// buffer = new MyBuffer(1024);
		buffer = new BufferManager(1024);
		//buffer.setPageLimit(1024);
		loggerTable = new Hashtable<Integer, Logger>();
		updateLogTable = new Hashtable<Integer, MyLog>();
	}

	/*
	 * This sets the number of pages of the buffer
	 */
	public DataManager(int bufferPageNum) {
		buffer = new BufferManager(bufferPageNum);
		loggerTable = new Hashtable<Integer, Logger>();
		updateLogTable = new Hashtable<Integer, MyLog>();
	}

	public DataManager(int pageLimit, int bufferPageNum) {
		this.pageLimit = pageLimit;
		buffer = new BufferManager(bufferPageNum);
		buffer.setPageLimit(pageLimit);
		loggerTable = new Hashtable<Integer, Logger>();
		updateLogTable = new Hashtable<Integer, MyLog>();
	}

	public int getPageLimit() {
		return pageLimit;
	}

	public void setPageLimit(int pageLimit) {
		this.pageLimit = pageLimit;
		buffer.setPageLimit(pageLimit);
	}

	public void setItemSize(int itemSize) {
		buffer.setItemSize(itemSize);
	}

	public void addFileOperator(String filename) {
		buffer.addFileOperator(filename);
	}

	public MyFileOperator getFileOperator(String filename) {
		return buffer.getFileOperator(filename);
	}

	/* This method reads data from the memory */
	private DataItem executeRead(MyCommand cmd) {
		return buffer.executeRead(cmd);
	}

	private boolean executeUpdate(MyCommand cmd) {
		File f = new File(cmd.getFilename());
		return buffer.executeUpdate(cmd);
	}

	private Hashtable<Integer, Logger> loggerTable;
	private Hashtable<Integer, MyLog> updateLogTable;

	private Hashtable<Integer, ArrayList<String>> deleteFiles = new Hashtable<Integer, ArrayList<String>>();

	public void addDeleteFiles(int id, String filename) {
		if (deleteFiles.containsKey(id)) {
			deleteFiles.get(id).add(filename);
		} else {
			ArrayList<String> files = new ArrayList<String>();
			files.add(filename);
			deleteFiles.put(id, files);
		}
	}
	
	public boolean hasDeleted(int id, String filename) {
		if(deleteFiles.containsKey(id)) {
			ArrayList<String> files = deleteFiles.get(id);
			for(int i = 0;i<files.size();i++) {
				if(files.get(i).equals(filename)) {
					return true;
				}
			}
		}
		return false;
	}
	
	public boolean execute(MyCommand cmd) {
		boolean success = true;
		Logger log;
		MyLog updateLog;
		int transactionid = cmd.getTransactionId();
		if (loggerTable.containsKey(transactionid)) {
			log = loggerTable.get(transactionid);
			updateLog = updateLogTable.get(transactionid);
		} else {
			log = Logger.getLogger("Transaction:" + transactionid);
			FileHandler fileHandler = null;
			try {
				System.out.println(transactionid);
				fileHandler = new FileHandler("TP_"+transactionid+".log", true);
				fileHandler.setFormatter(new SimpleFormatter());
			} catch (SecurityException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			} catch (IOException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}        
	        log.addHandler(fileHandler);
			loggerTable.put(transactionid, log);
			updateLog = new MyLog(transactionid);
			updateLogTable.put(transactionid, updateLog);
		}
		if (cmd.isBeginCommand()) {
			BeginCommand bc = (BeginCommand) cmd;
			if (bc.isTransaction()) {
				log.info(transactionid +","+cmd.getTimeStamp()+ ":Transaction started");
			} else {
				log.info(transactionid+","+cmd.getTimeStamp() + "Process started");
			}
		} else if (cmd.isReadCommand()) {
			DataItem item = executeRead(cmd);
			String filename = cmd.getFilename();
			int id = cmd.getId();
			if (item != null) {
				log.info(transactionid +","+cmd.getTimeStamp()+ ":READ " + filename + "," + id + ":"
						+ item.toString());
			} else {
				log.info(transactionid+","+cmd.getTimeStamp() + ":READ " + filename + "," + id
						+ ":NULL");
			}
			success = true;

		} else if (cmd.isMatchCommand()) {
			ArrayList<DataItem> items = buffer.matchItem(cmd.getFilename(),
					cmd.getAreacode());
			String msg = "";
			for (int i = 0; i < items.size(); i++) {
				msg += items.get(i).toString() + "\n";
			}
			log.info(transactionid+","+cmd.getTimeStamp() + ": MATCH " + cmd.getFilename() + ","
					+ cmd.getId() + ":\n" + msg);
			success = true;
		} else if (cmd.isWriteCommand()) {
			String filename = cmd.getFilename();
			buffer.createNewFile(filename);
			DataItem beforeItem = buffer.queryItem(filename, cmd.getDataItem()
					.getId());
			BeforeImage bfImage = new BeforeImage();
			bfImage.setItemId(cmd.getDataItem().getId());
			bfImage.setType(BeforeImage.DATA_TYPE);
			bfImage.setItem(beforeItem);
			bfImage.setDstfilename(filename);
			updateLog.addBeforeImage(bfImage);
			success = executeUpdate(cmd);
			log.info(transactionid+","+cmd.getTimeStamp() + ": WRITE " + filename + ":"
					+ cmd.getDataItem().toString() + ",success:" + success);

		} else if (cmd.isDeleteCommand()) {
			/*
			 * When performing a delete command here, we need to keep a copy of
			 * the current page However, if there are multiple delete of one
			 * file, do we need to keep the copy every time? Actually, we only
			 * need to keep the initial version for undo For the rest, we just
			 * remove the current file, and do the operations
			 */
			String filename = cmd.getFilename();
			backupFile(filename);
			BeforeImage bfImage = new BeforeImage();
		    if(!hasDeleted(cmd.getTransactionId(),filename)) {
		    	buffer.backUpFile(filename, bfImage);
		    }
			bfImage.setType(BeforeImage.FILE_TYPE);
			bfImage.setDstfilename(filename);
			updateLog.addBeforeImage(bfImage);
			success = deleteFile(filename);
			addDeleteFiles(cmd.getTransactionId(), filename);
			log.info(transactionid +","+cmd.getTimeStamp()+ ": DELETE " + cmd.getFilename()
					+ ",success:" + success);
		} else if (cmd.isAbortCommand()) {
			/*
			 * Aborting, do all the undo stuff
			 */
			ArrayList<BeforeImage> beforeImages = updateLog.getBeforeImages();
			ArrayList<BeforeImage> undoList = combineImages(beforeImages);
			for (int i = undoList.size() - 1; i >= 0; i--) {
				BeforeImage bfImage = undoList.get(i);
				success = undoImage(bfImage);
			}
			log.info(transactionid+","+cmd.getTimeStamp() + ":Transaction aborted");
		} else if (cmd.isCommitCommand()) {
			ArrayList<String> files = deleteFiles.get(cmd.getTransactionId());
			if (files != null) {
				for (int i = 0; i < files.size(); i++) {
					success = deleteFile(files.get(i) + ".tmp");
				}
			}
			log.info(transactionid+","+cmd.getTimeStamp() + ":Transaction commit");
		}
		return success;
	}

	public String makeKey(String filename, int id) {
		return filename + "_" + id;
	}

	public boolean undoImage(BeforeImage image) {
		if (image.getType() == BeforeImage.DATA_TYPE) {
			if(image.getItem()!=null) {
				return buffer.writeItem(image.getItem(), image.getDstfilename());
			} else {
				return buffer.writeNullItem(image.getItemId(),image.getDstfilename());
			}
		} else {
			String filename = image.getDstfilename();
			restoreFile(filename);
		}
		return true;
	}

	public ArrayList<BeforeImage> combineImages(
			ArrayList<BeforeImage> beforeImages) {
		ArrayList<BeforeImage> combinedImages = new ArrayList<BeforeImage>();
		HashSet<String> deleteFileSet = new HashSet<String>();
		HashSet<String> writeRowSet = new HashSet<String>();
		for (int i = 0; i < beforeImages.size(); i++) {
			BeforeImage image = beforeImages.get(i);
			String filename = image.getDstfilename();
			if (!deleteFileSet.contains(filename)) {
				if (image.getType() == BeforeImage.FILE_TYPE) {
					deleteFileSet.add(filename);
					combinedImages.add(image);
				} else {
					String key = makeKey(filename, image.getItemId());
					if (!writeRowSet.contains(key)) {
						writeRowSet.add(key);
						combinedImages.add(image);
					}
				}
			}
		}
		return combinedImages;
	}

	/*
	 * Backing up the file, if there is already a backup, do not do the
	 * operations. this is just the backup of harddisk files
	 */
	public boolean backupFile(String filename) {
		String dstFile = filename + ".tmp";
		File f = new File(dstFile);
		if (f.exists())
			return false;
		return DBUtils.copyfile(filename, dstFile);
	}

	public boolean restoreFile(String filename) {
		boolean success = true;
		String srcFile = filename + ".tmp";
		deleteFile(filename);// delete the current version of file
		success = DBUtils.copyfile(srcFile, filename);// copy the tmp file back
		success = buffer.restoreBuffer(filename);
		success = buffer.deleteFile(srcFile);
		return success;
	}

	public boolean deleteFile(String filename) {
		return buffer.deleteFile(filename);
	}

	public boolean exit() {
		return buffer.flushAll();
	}
}
